import socket
import threading
from queue import Queue
import os
import sys

# Redis INFO命令的协议格式数据包（官方协议）[4](@ref)
REDIS_INFO_PACKET = b'*1\r\n$4\r\nINFO\r\n'


def check_redis(host, port=6379, timeout=5):
    """
    检测Redis未授权访问漏洞
    原理：发送Redis协议格式的INFO命令，通过响应判断是否存在未授权访问[4,10](@ref)
    """
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        sock.connect((host, port))

        # 发送Redis协议格式的INFO命令[4](@ref)
        sock.send(REDIS_INFO_PACKET)
        response = sock.recv(1024).decode('utf-8', errors='ignore')

        # 漏洞判断标准[3,10](@ref)
        if 'redis_version' in response and 'NOAUTH' not in response:
            return True, f"{host}:{port}"
        return False, ""

    except (socket.error, socket.timeout, ConnectionRefusedError):
        return False, ""
    finally:
        try:
            sock.close()
        except:
            pass


def worker(target_queue, vulnerable_list, lock):
    """多线程检测工作函数（线程安全）"""
    while not target_queue.empty():
        host, port = target_queue.get()
        vulnerable, result = check_redis(host, port)
        if vulnerable:
            with lock:
                vulnerable_list.append(result)
        target_queue.task_done()


def save_results(vulnerable_list):
    """保存结果到文件（追加模式）[6,8](@ref)"""
    try:
        with open('redis未授权.txt', 'a', encoding='utf-8') as f:
            for url in vulnerable_list:
                f.write(f"{url}\n")
        return True
    except Exception as e:
        print(f"\n[错误] 保存文件失败: {str(e)}")
        return False


def main():
    # 检查目标文件是否存在[8](@ref)
    if not os.path.exists('redis_url.txt'):
        print("[错误] 未找到 redis_url.txt 文件")
        return

    # 读取目标文件
    targets = []
    try:
        with open('redis_url.txt', 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue

                if ':' in line:
                    parts = line.split(':', 1)
                    host = parts[0]
                    port = int(parts[1]) if parts[1].isdigit() else 6379
                else:
                    host = line
                    port = 6379
                targets.append((host, port))
    except Exception as e:
        print(f"[错误] 读取文件失败: {str(e)}")
        return

    # 多线程检测
    target_queue = Queue()
    vulnerable_list = []
    lock = threading.Lock()

    for target in targets:
        target_queue.put(target)

    # 创建线程（最多20个）
    thread_count = min(20, len(targets))
    threads = []
    for _ in range(thread_count):
        t = threading.Thread(target=worker, args=(target_queue, vulnerable_list, lock))
        t.daemon = True
        t.start()
        threads.append(t)

    target_queue.join()

    # 输出结果
    if vulnerable_list:
        print("\n[+] 存在Redis未授权访问漏洞的目标:")
        for i, url in enumerate(vulnerable_list, 1):
            print(f"  {i}. {url}")

        # 询问用户是否保存结果
        user_input = input("\n[?] 是否将结果保存到 redis未授权.txt? (y/n): ").strip().lower()
        if user_input == 'y':
            if save_results(vulnerable_list):
                print("[+] 结果已追加保存至 redis未授权.txt")
            else:
                print("[-] 结果保存失败")
        else:
            print("[-] 结果未保存")
    else:
        print("\n[-] 未发现Redis未授权访问漏洞")


if __name__ == "__main__":
    print("开始检测Redis未授权访问漏洞...")
    main()